/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.report;

import com.eviware.soapui.model.TestModelItem;
import com.eviware.soapui.model.testsuite.ProjectRunContext;
import com.eviware.soapui.model.testsuite.ProjectRunListener;
import com.eviware.soapui.model.testsuite.ProjectRunner;
import com.eviware.soapui.model.testsuite.TestCase;
import com.eviware.soapui.model.testsuite.TestCaseRunContext;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.model.testsuite.TestRunListener;
import com.eviware.soapui.model.testsuite.TestRunner.Status;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.model.testsuite.TestStepResult;
import com.eviware.soapui.model.testsuite.TestStepResult.TestStepStatus;
import com.eviware.soapui.model.testsuite.TestSuite;
import com.eviware.soapui.model.testsuite.TestSuiteRunContext;
import com.eviware.soapui.model.testsuite.TestSuiteRunListener;
import com.eviware.soapui.model.testsuite.TestSuiteRunner;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.xml.XmlUtils;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Collects TestRun results and creates JUnitReports
 *
 * @author ole.matzura
 */

public class JUnitReportCollector implements TestRunListener, TestSuiteRunListener, ProjectRunListener {
    HashMap<String, JUnitReport> reports;
    HashMap<TestCase, String> failures;
    HashMap<TestCase, Integer> errorCount;

    protected boolean includeTestPropertiesInReport = false;
    private int maxErrors = 0;


    public JUnitReportCollector() {
        this(0);
    }

    public JUnitReportCollector(int maxErrors) {
        this.maxErrors = maxErrors;
        reports = new HashMap<String, JUnitReport>();
        errorCount = new HashMap<TestCase, Integer>();
        failures = new HashMap<TestCase, String>();
    }

    public List<String> saveReports(String path) throws Exception {

        File file = new File(path);
        if (!file.exists() || !file.isDirectory()) {
            file.mkdirs();
        }

        List<String> result = new ArrayList<String>();

        Iterator<String> keyset = reports.keySet().iterator();
        while (keyset.hasNext()) {
            String name = keyset.next();
            JUnitReport report = reports.get(name);
            String fileName = path + File.separatorChar + "TEST-" + StringUtils.createFileName(name, '_') + ".xml";
            saveReport(report, fileName);
            result.add(fileName);
        }

        return result;
    }

    public HashMap<String, JUnitReport> getReports() {
        return reports;
    }

    public void saveReport(JUnitReport report, String filename) throws Exception {
        report.save(new File(filename));
    }

    public String getReport() {
        Set<String> keys = reports.keySet();
        if (keys.size() > 0) {
            String key = (String) keys.toArray()[0];
            return reports.get(key).toString();
        }
        return "No reports..:";
    }

    public void afterRun(TestCaseRunner testRunner, TestCaseRunContext runContext) {
        TestCase testCase = testRunner.getTestCase();
        JUnitReport report = reports.get(testCase.getTestSuite().getName());

        HashMap<String, String> testProperties = getTestPropertiesAsHashMap(testCase);

        if (Status.INITIALIZED != testRunner.getStatus() && Status.RUNNING != testRunner.getStatus()) {
            if (Status.CANCELED == testRunner.getStatus()) {
                report.addTestCase(testCase.getName(), testRunner.getTimeTaken(), testProperties);
            }
            if (Status.FAILED == testRunner.getStatus()) {
                String msg = "";
                if (failures.containsKey(testCase)) {
                    msg = failures.get(testCase).toString();
                }
                report.addTestCaseWithFailure(testCase.getName(), testRunner.getTimeTaken(), testRunner.getReason(), msg, testProperties);
            }
            if (Status.FINISHED == testRunner.getStatus()) {
                report.addTestCase(testCase.getName(), testRunner.getTimeTaken(), testProperties);
            }

        }
    }

    protected HashMap<String, String> getTestPropertiesAsHashMap(TestModelItem testCase) {
        HashMap<String, String> testProperties = new HashMap<>();
        for (Map.Entry<String, TestProperty> stringTestPropertyEntry : testCase.getProperties().entrySet()) {
            testProperties.put(stringTestPropertyEntry.getKey(), stringTestPropertyEntry.getValue().getValue());
        }
        return testProperties;
    }

    public void afterStep(TestCaseRunner testRunner, TestCaseRunContext runContext, TestStepResult result) {
        TestStep currentStep = result.getTestStep();
        TestCase testCase = currentStep.getTestCase();

        if (result.getStatus() == TestStepStatus.FAILED) {
            if (maxErrors > 0) {
                Integer errors = errorCount.get(testCase);
                if (errors == null) {
                    errors = 0;
                }

                if (errors >= maxErrors) {
                    return;
                }

                errorCount.put(testCase, errors + 1);
            }

            StringBuffer buf = new StringBuffer();
            if (failures.containsKey(testCase)) {
                buf.append(failures.get(testCase));
            }

            buf.append("<h3><b>").append(XmlUtils.entitize(result.getTestStep().getName()))
                    .append(" Failed</b></h3><pre>");
            for (String message : result.getMessages()) {
                if (message.toLowerCase().startsWith("url:")) {
                    String url = XmlUtils.entitize(message.substring(4).trim());
                    buf.append("URL: <a target=\"new\" href=\"").append(url).append("\">").append(url)
                            .append("</a>");
                } else {
                    buf.append(message);
                }

                buf.append("\r\n");
            }

            // use string value since constant is defined in pro.. duh..
            if (testRunner.getTestCase().getSettings().getBoolean("Complete Error Logs")) {
                StringWriter stringWriter = new StringWriter();
                PrintWriter writer = new PrintWriter(stringWriter);
                result.writeTo(writer);

                buf.append(XmlUtils.entitize(stringWriter.toString()));
            }

            buf.append("</pre><hr/>");

            failures.put(testCase, buf.toString());
        }
    }

    public void beforeRun(TestCaseRunner testRunner, TestCaseRunContext runContext) {
        TestCase testCase = testRunner.getTestCase();
        TestSuite testSuite = testCase.getTestSuite();
        if (!reports.containsKey(testSuite.getName())) {
            JUnitReport report = new JUnitReport();
            report.setIncludeTestProperties(this.includeTestPropertiesInReport);
            report.setTestSuiteName(testSuite.getProject().getName() + "." + testSuite.getName());
            reports.put(testSuite.getName(), report);
        }
    }

    public void beforeStep(TestCaseRunner testRunner, TestCaseRunContext runContext) {
    }

    public void beforeStep(TestCaseRunner testRunner, TestCaseRunContext runContext, TestStep testStep) {
    }

    public void reset() {
        reports.clear();
        failures.clear();
        errorCount.clear();
    }

    public void afterRun(TestSuiteRunner testRunner, TestSuiteRunContext runContext) {
    }

    public void afterTestCase(TestSuiteRunner testRunner, TestSuiteRunContext runContext, TestCaseRunner testCaseRunner) {
        testCaseRunner.getTestCase().removeTestRunListener(this);
    }

    public void beforeRun(TestSuiteRunner testRunner, TestSuiteRunContext runContext) {
    }

    public void beforeTestCase(TestSuiteRunner testRunner, TestSuiteRunContext runContext, TestCase testCase) {
        testCase.addTestRunListener(this);
    }

    public void afterRun(ProjectRunner testScenarioRunner, ProjectRunContext runContext) {
    }

    public void afterTestSuite(ProjectRunner testScenarioRunner, ProjectRunContext runContext,
                               TestSuiteRunner testRunner) {
        testRunner.getTestSuite().removeTestSuiteRunListener(this);
    }

    public void beforeRun(ProjectRunner testScenarioRunner, ProjectRunContext runContext) {
    }

    public void beforeTestSuite(ProjectRunner testScenarioRunner, ProjectRunContext runContext, TestSuite testSuite) {
        testSuite.addTestSuiteRunListener(this);
    }

    /**
     * Use this factory method to allow usage of an external reportCollecto; it
     * checks for a soapui.junit.reportCollector system property that should
     * specify a class derived from this JUnitReportCollector which will be used
     * instead
     *
     * @param maxErrors
     */

    public static JUnitReportCollector createNew(int maxErrors) {
        String className = System.getProperty("soapui.junit.reportCollector", null);
        if (StringUtils.hasContent(className)) {
            try {
                return (JUnitReportCollector) Class.forName(className).getConstructor(Integer.class)
                        .newInstance(maxErrors);
            } catch (Exception e) {
                System.err.println("Failed to create JUnitReportCollector class [" + className + "]; " + e.toString());
            }
        }

        return new JUnitReportCollector(maxErrors);
    }

    public void setIncludeTestPropertiesInReport(boolean includeTestPropertiesInReport) {
        this.includeTestPropertiesInReport = includeTestPropertiesInReport;
    }

}
